In [1]:
import sys, os, time
import usb.core
import usb.util

In [2]:
#Throw this into a module! import fl593b as ?

# DEVICE CONSTANTS
VENDOR_ID     = 0x1a45
PRODUCT_ID    = 0x2001
CONFIGURATION = 1
EP_ADDR_OUT   = 0x01
EP_PACK_OUT   = 20
EP_ADDR_IN    = 0x82
EP_PACK_IN    = 21
TIMEOUT       = 100  # default timeout of read/write is 1000 ms

# WEISUB protocol constants
# end codes
ERR_OK      = 0x00  # no error
ERR_DEVTYPE = 0x01  # device type field incorrect
ERR_CHANNEL = 0x02  # channel number out of range
ERR_OPTYPE  = 0x03  # optype not supported
ERR_NITIMPL = 0x04  # opcode not implemented
ERR_PENDING = 0x05  # command received, but not completed [ignore data]
ERR_BUSY    = 0x06  # device currently busy, has not performed requested op
ERR_DATA    = 0x07  # data field content invalid for opcode
ERR_SAFETY  = 0x08  # requested op not within safety specs of config
ERR_CALMODE = 0x09  # requested op only available in calibration mode!

# Channels
NUM_CHAN = 2  # (0: device, 1: channel 1, 2: channel 2)

# OpTypes
TYPE_READ  = 0x01  # return OpCode quantity to host
TYPE_WRITE = 0x02  # write OpCode  quantity to device
TYPE_MIN   = 0x03  # return minimum of OpCode quantity to host
TYPE_MAX   = 0x04  # return maximum of OpCode quantity to host

# General OpCodes
# r = read, w = write
CMD_MODEL    = 0x00  # r
CMD_SERIAL   = 0x01  # rw
CMD_FWVER    = 0x02  # r
CMD_DEVTYPE  = 0x03  # r
CMD_CHANCT   = 0x04  # r
CMD_IDENTIFY = 0x05  # rw
CMD_SAVE     = 0x0C  # w
CMD_RECALL   = 0x0D  # w
CMD_PASSWD   = 0x0E  # rw
CMD_REVERT   = 0x0F  # w

# Specific OpCodes (codes > 0x10 device specific)
# r = read, w = write, mm = min/max
CMD_ALARM    = 0x10  # r, Alarm flags
CMD_SETPOINT = 0x11  # rwm, Setpoint, units depend on device MODE!
CMD_LIMIT    = 0x12  # rwm, current limit (Ampere, applies for CC, CP and analog modulation!)
CMD_MODE     = 0x13  # rw, feedback mode
CMD_TRACK    = 0x13  # rw, tracking configuration <- ??? what is this?
CMD_IMON     = 0x15  # current monitor
CMD_PMON     = 0x16  # power monitor
CMD_ENABLE   = 0x17  # rw, output enable. Additionally requires the output enable switch
                     # on the board to be set to EN, and the NT pin to be pulled LOW
CMD_RPD      = 0x19  # rwm, kOhm, photodiode feedback resistor for specified channel
CMD_CAL_ISCALE = 0xE2  # rw, current monitor calibration scaling value for selected channel

# ALARM FLAGS
ALARM_FLAG_DICT = {0: 'output',   # output status. 0: off, 1: on; equals XEN*LEN*REN
                   1: 'XEN',      # external enable flag, 0: J102 floating or HIGH, 1: LOW
                   2: 'LEN',      # local enable flag, 0: S100 enabled, 0: disabled
                   3: 'REN',      # remote enable flag, enable command state
                   4: 'MODE1',    # feedback more channel 1, 0: CC, 1: CP
                   5: 'MODE2',    # feedback more channel 1, 0: CC, 1: CP
                   6: 'PARA',     # parallel mode, state of track command, 0: independently
                   7: 'IDENT',    # status identify flag, 0: inactive, 1: active
                   8: 'WRITE',    # write to non-volatile memory in progress following SAVE
                   9: 'CALMODE'}  # 1: device is in calibration mode, entered with PASSWD,
                                  # and left with REVERT

# DATA (data ignored for types read, min and max)
LEN_DATA = 16    # length data field in bytes
FLAG_OFF = 0x30  # ASCII 48, '0'
FLAG_ON  = 0x31  # ASCII 49, '1'

In [3]:
def attach_fl593(configuration=1):
    """bConfigurationValue: 1"""
    dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID)
    if dev is None:
        raise ValueError('Device not found')
    # Not necessary, as this is not an HID?
    try:
        dev.detach_kernel_driver(0)
    except: # this usually mean that kernel driver has already been dettached
        pass
    dev.set_configuration(configuration)
        
    show_descriptors(dev)
    return dev

In [4]:
def show_descriptors(device):
    """Output all available configurations.
    """
    for cfg in device:
        sys.stdout.write(str(cfg.bConfigurationValue) + '\n')

In [5]:
def pack_request(devtype=0x0, channel=0x0, optype=TYPE_READ, \
                 opcode=CMD_MODEL, data=[0x0]*LEN_DATA,packlen=EP_PACK_OUT):
    """Pack bytes given as arguments into a string of length 'packlen' which
    may vary depending on the targeted endpoint.
    
    devtype can be ignored (there is only one device on the FL593B)
    """
    packet = [devtype, channel, optype, opcode]
    packet.extend(data)
    
    # add padding if data not covering full length
    if len(packet) < packlen:
        packet.extend([0x00]*(packlen-len(packet)))
        
    return ''.join([chr(c) for c in packet])

In [6]:
def log_error(functionName, ret):
    """Log error to standard error output
    """
    sys.stderr.write(functionName + (" failed with return code %d\n" % ret))

In [7]:
def show_command(bytes):
    """Logs command onto standard output.
    """
    sys.stdout.write('DevType: %d\nChannel: %d\nOpType:  %d\nopCode:  %d\nData:    %s\n'%\
                      (ord(bytes[0]), ord(bytes[1]), ord(bytes[2]), ord(bytes[3]), bytes[4:]))

In [8]:
def show_response(bytes):
    """Logs response onto standard output.
    """
    sys.stdout.write('DevType: %d\nChannel: %d\nOpType:  %d\nopCode:  %d\nEndCode: %d\nData:    %s\n'%\
                      (bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5:].tostring()))

In [9]:
def show_alarms(bytes):
    sys.stdout.write('\nAlarm flag states: \n')
    for i in range(10):
        sys.stdout.write('%s: %s\n' % (ALARM_FLAG_DICT[i],
                                      'ON' if bytes[i] == FLAG_ON else 'OFF'))

In [10]:
def roundtrip(cmd_packet):
    # Write command packet
    try:
        bw = ep_out.write(cmd_packet)
        print "\n-> OUT %d Bytes" % bw
        show_command(cmd_packet)
    except usb.USBError as error:
        raise error
        
    # Read back result
    try:
        resp_packet = ep_in.read(EP_PACK_IN, 100)
        print "<- IN  %d Bytes\n------------" % len(resp_packet)
        show_response(resp_packet)
    except usb.USBError as error:
        raise error
    return resp_packet

THE DEVICE


In [11]:
# USB device
dev = attach_fl593()

# configuration
cfg = dev.get_active_configuration()

# interface
intf = cfg[(0,0)]

# endpoint
ep_out, ep_in = usb.util.find_descriptor(intf, find_all=True);


1

READING

The easy part. At least as long as everything and everyone behaves nicely.


In [12]:
# predefine useful packets for testing...
read_model = pack_request(optype=TYPE_READ, opcode=CMD_MODEL)
read_fwver = pack_request(optype=TYPE_READ, opcode=CMD_FWVER)
read_chanct = pack_request(optype=TYPE_READ, opcode=CMD_CHANCT)
read_devtype = pack_request(optype=TYPE_READ, opcode=CMD_DEVTYPE)
read_serial = pack_request(optype=TYPE_READ, opcode=CMD_SERIAL)
read_enable = pack_request(optype=TYPE_READ, opcode=CMD_ENABLE)
read_alarm = pack_request(optype=TYPE_READ, opcode=CMD_ALARM)

read_imon = {1: pack_request(optype=TYPE_READ, opcode=CMD_IMON, channel=1),
             2: pack_request(optype=TYPE_READ, opcode=CMD_IMON, channel=2)}
read_imax = {1: pack_request(optype=TYPE_MAX, opcode=CMD_IMON, channel=1),
             2: pack_request(optype=TYPE_MAX, opcode=CMD_IMON, channel=2)}
read_imin = {1: pack_request(optype=TYPE_MIN, opcode=CMD_IMON, channel=1),
             2: pack_request(optype=TYPE_MIN, opcode=CMD_IMON, channel=2)}

# example:
roundtrip(read_model)
roundtrip(read_imon[2])


-> OUT 20 Bytes
DevType: 0
Channel: 0
OpType:  1
opCode:  0
Data:    
<- IN  21 Bytes
------------
DevType: 0
Channel: 0
OpType:  1
opCode:  0
EndCode: 0
Data:    FL593B

-> OUT 20 Bytes
DevType: 0
Channel: 2
OpType:  1
opCode:  21
Data:    
<- IN  21 Bytes
------------
DevType: 0
Channel: 2
OpType:  1
opCode:  21
EndCode: 0
Data:    -0.001047
Out[12]:
array('B', [0, 2, 1, 21, 0, 45, 48, 46, 48, 48, 49, 48, 52, 55, 0, 0, 0, 0, 0, 0, 0])

WRITING

Requires properly formatted data


In [19]:
write_enable = pack_request(optype=TYPE_WRITE, opcode=CMD_ENABLE, data=[FLAG_ON])

In [20]:
roundtrip(read_model)


-> OUT 20 Bytes
DevType: 0
Channel: 0
OpType:  1
opCode:  0
Data:    
<- IN  21 Bytes
------------
DevType: 0
Channel: 0
OpType:  1
opCode:  0
EndCode: 0
Data:    FL593B
Out[20]:
array('B', [0, 0, 1, 0, 0, 70, 76, 53, 57, 51, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

In [21]:
roundtrip(read_enable)
roundtrip(write_enable);


-> OUT 20 Bytes
DevType: 0
Channel: 0
OpType:  1
opCode:  23
Data:    
<- IN  21 Bytes
------------
DevType: 0
Channel: 0
OpType:  1
opCode:  23
EndCode: 0
Data:    0

-> OUT 20 Bytes
DevType: 0
Channel: 0
OpType:  2
opCode:  23
Data:    1
<- IN  21 Bytes
------------
DevType: 0
Channel: 0
OpType:  2
opCode:  23
EndCode: 0
Data:    1

ALARM FLAGS


In [22]:
show_alarms(roundtrip(read_alarm)[5:])


-> OUT 20 Bytes
DevType: 0
Channel: 0
OpType:  1
opCode:  16
Data:    
<- IN  21 Bytes
------------
DevType: 0
Channel: 0
OpType:  1
opCode:  16
EndCode: 0
Data:    1111000000

Alarm flag states: 
output: ON
XEN: ON
LEN: ON
REN: ON
MODE1: OFF
MODE2: OFF
PARA: OFF
IDENT: OFF
WRITE: OFF
CALMODE: OFF

In [23]:
del dev

In [ ]: